﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;

namespace WeatherData
{
    public class Metar
    {
        private const string DATA_URL = "http://weather.aero/dataserver_current/httpparam?dataSource=metars&requestType=retrieve&format=xml&stationString={0}&hoursBeforeNow=1&mostRecent=true";

        private XElement root;
        private XElement data;
        private XElement metar;

        public int Results { get; private set; }
        public string RawData { get; private set; }
        public string StationId { get; private set; }
        public DateTime ObservationTime { get; private set; }
        public double Latitude { get; private set; }
        public double Longitude { get; private set; }
        public double Temperature { get; private set; }
        public double DewPoint { get; private set; }
        public int WindDirection { get; private set; }
        public int WindSpeed { get; private set; }
        public int WindGusts { get; private set; }
        public double Visibility { get; private set; }
        public double Altimeter { get; private set; }
        public double SeaLevelPressure { get; private set; }
        public string Precipitation { get; private set; }
        public double PrecipitationQuantity { get; private set; }
        public string FlightConditions { get; private set; }
        public List<SkyCondition> SkyConditions { get; private set; }
        public bool Available
        {
            get
            {
                return this.Results > 0;
            }
        }

        public Metar(string targetStation)
        {
            this.StationId = targetStation;
            this.GetCurrentReport();
        }

        private void GetCurrentReport()
        {
            this.root = XElement.Load(String.Format(DATA_URL, this.StationId));
            this.data = this.root.Element("data");
            this.metar = this.data.Element("METAR");

            this.Results = this.GetStationCount();
            if (this.Results > 0)
            {
                this.RawData = this.GetRawText();
                this.ObservationTime = this.GetObservationTime();
                this.Latitude = this.GetLatitude();
                this.Longitude = this.GetLongitude();
                this.Temperature = this.GetTemp();
                this.DewPoint = this.GetDewPoint();
                this.WindDirection = this.GetWindDirection();
                this.WindSpeed = this.GetWindSpeed();
                this.WindGusts = this.GetWindGust();
                this.Visibility = this.GetVisibility();
                this.Altimeter = this.GetAlitmeter();
                this.SeaLevelPressure = this.GetSeaLevelPressure();
                this.Precipitation = this.GetPrecipitationText();
                this.PrecipitationQuantity = this.GetPrecipQty();
                this.FlightConditions = this.GetFlightCategory();

                this.GetSkyConditions();
            }
        }

        private int GetStationCount()
        {
            XAttribute a = this.data.Attribute("num_results");
            return int.Parse(a.Value);
        }
        private string GetRawText()
        {
            XElement el = this.metar.Element("raw_text");
            return el.Value;
        }
        private DateTime GetObservationTime()
        {
            XElement el = this.metar.Element("observation_time");
            return DateTime.Parse(el.Value);
        }
        private double GetLatitude()
        {
            XElement el = this.metar.Element("latitude");
            return double.Parse(el.Value);
        }
        private double GetLongitude()
        {
            XElement el = this.metar.Element("longitude");
            return double.Parse(el.Value);
        }
        private double GetTemp()
        {
            XElement el = this.metar.Element("temp_c");
            return double.Parse(el.Value);
        }
        private double GetDewPoint()
        {
            XElement el = this.metar.Element("dewpoint_c");
            return double.Parse(el.Value);
        }
        private int GetWindDirection()
        {
            XElement el = this.metar.Element("wind_dir_degrees");
            return int.Parse(el.Value);
        }
        private int GetWindSpeed()
        {
            XElement el = this.metar.Element("wind_speed_kt");
            return int.Parse(el.Value);
        }
        private int GetWindGust()
        {
            XElement el = this.metar.Element("wind_gust_kt");
            if (el != null)
                return int.Parse(el.Value);
            else
                return 0;
        }
        private double GetVisibility()
        {
            XElement el = this.metar.Element("visibility_statute_mi");
            return double.Parse(el.Value);
        }
        private double GetAlitmeter()
        {
            XElement el = this.metar.Element("altim_in_hg");
            return double.Parse(el.Value);
        }
        private double GetSeaLevelPressure()
        {
            XElement el = this.metar.Element("sea_level_pressure_mb");
            if (el != null)
                return double.Parse(el.Value);
            else
                return 0.0;
        }
        private string GetPrecipitationText()
        {
            XElement el = this.metar.Element("wx_string");
            if (el != null)
                return el.Value;
            else
                return String.Empty;
        }
        private double GetPrecipQty()
        {
            XElement el = this.metar.Element("precip_in");
            if (el != null)
                return double.Parse(el.Value);
            else
                return 0.0;
        }
        private string GetFlightCategory()
        {
            XElement el = this.metar.Element("flight_category");
            return el.Value;
        }

        private void GetSkyConditions()
        {
            IEnumerable<XElement> conds = from skyconds in this.metar.Elements("sky_condition")
                                          select skyconds;

            this.SkyConditions = new List<SkyCondition>();
            bool isCeilingSet = false;
            foreach (XElement cond in conds)
            {
                SkyCondition c = new SkyCondition();
                if (cond.Attribute("cloud_base_ft_agl") != null)
                    c.Base = int.Parse(cond.Attribute("cloud_base_ft_agl").Value);
                c.Cover = SkyCondition.ParseSkyCover(cond.Attribute("sky_cover").Value);

                if (!isCeilingSet)
                    isCeilingSet = this.SetIsCeiling(c);

                this.SkyConditions.Add(c);
            }
        }

        private bool SetIsCeiling(SkyCondition c)
        {
            if(!(c.Cover == SkyCover.Overcast || c.Cover == SkyCover.Broken))
                return false;

            c.IsCeiling = true;

            return true;
        }
    }

    public class SkyCondition
    {
        public int Base;
        public SkyCover Cover;
        public bool IsCeiling { get; internal set; }

        public static SkyCover ParseSkyCover(string value)
        {
            switch (value)
            {
                case "SKC":
                case "CLR":
                    return SkyCover.Clear;
                case "FEW":
                    return SkyCover.Few;
                case "SCT":
                    return SkyCover.Scattered;
                case "BKN":
                    return SkyCover.Broken;
                case "OVC":
                    return SkyCover.Overcast;
                default:
                    return SkyCover.Unknown;
            }
        }

        public override string ToString()
        {
            if (this.Cover == SkyCover.Clear)
            {
                return "Sky clear";
            }
            else
            {
                string value = String.Format("{0} feet {1}", this.Base, this.Cover.ToString());

                if (this.IsCeiling)
                    return String.Format("{0} (ceiling)", value);
                else
                    return value;
            }
        }
    }
}